﻿//  Symplectic Spark
//  Copyright 2010 Symplectic Ltd
//  Created by Martyn Whitwell (martyn@symplectic.co.uk)

//  This file is part of Spark.

//  Spark is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.

//  Spark is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.

//  You should have received a copy of the GNU General Public License
//  along with Spark.  If not, see <http://www.gnu.org/licenses/>.

// $URL: https://symplectic-spark.googlecode.com/svn/trunk/website/App_Code/Expression.cs $
// $LastChangedDate: 2010-03-04 16:37:03 +0000 (Thu, 04 Mar 2010) $
// $LastChangedRevision: 21 $
// $LastChangedBy: martyn@symplectic.co.uk $

using System;
using System.Linq;
using System.Xml.Linq;
using System.Text.RegularExpressions;

/// <summary>
/// Summary description for Component
/// </summary>
namespace Symplectic.Spark
{
    public abstract class Expression : BaseType
    {
        #region Logger
        private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        #endregion
           


        public static XElement Evaluate(XElement value, RequestSpec requestSpec)
        {
            XElement chosenValue = null;

            switch (value.Name.LocalName)
            {
                case "when":
                case "if":
                    string left = readAttributeStringStatic(value, "left", false);
                    string condition = readAttributeStringStatic(value, "condition", false);
                    string right = readAttributeStringStatic(value, "right", false);

                    if (test(left, condition, right, requestSpec))
                        chosenValue = value.Elements().First();
                    else
                        chosenValue = null;
                    break;

                case "choose":
                    foreach (XElement when in value.Elements("when"))
                    {
                        chosenValue = Evaluate(when, requestSpec); //recursive!
                        if (chosenValue != null)
                            break;
                    }
                    break;
                default:
                    throw new ArgumentException("Unable to parse " + value.Name.LocalName + " as an expression (" + value.ToString() + ")");
            }

            //Logger.DebugFormat("answer = {0}", chosenValue);
            return chosenValue;
        }

        private static bool test(string left, string condition, string right, RequestSpec requestSpec)
        {
//            Logger.DebugFormat("Testing {0} {1} {2}", left, condition, right);

            if (left == null)
                throw new Exception("left attribute is not specified");
            if (condition == null)
                throw new Exception("condition attribute is not specified");
            //right value is not required for all operators
            
            switch (condition)
            {
                case "equal":
                    return GetOperandValue(left, requestSpec).Trim().Equals(GetOperandValue(right, requestSpec).Trim());
                case "not-equal":
                    return !GetOperandValue(left,  requestSpec).Trim().Equals(GetOperandValue(right,  requestSpec).Trim());
                case "empty":
                    return GetOperandValue(left, requestSpec).Trim().Equals(string.Empty);
                case "not-empty":
                    return !GetOperandValue(left, requestSpec).Trim().Equals(string.Empty);
                default:
                    throw new ArgumentException("Unable to parse condition: " + condition, "condition");
            }
        }

        public static string GetOperandValue(string operand, RequestSpec requestSpec)
        {
            /* possible values and their meaning
             [[component]]
             {{requestSpec}}
             ((internalFunction))
             literal value
            */

            string output = null;

            if (operand == null)
                output = string.Empty;
            else
            {
                string trimmedOperand = operand.Trim();
                Match match = null;
                bool matched = false;

                if (!matched)
                {
                    match = Global.RegexComponent.Match(trimmedOperand);
                    if (match.Groups["name"].Success)
                    {
                        matched = true;
                        output = Global.Components[match.Groups["name"].Value].GetValue(requestSpec);
                    }
                }

                if (!matched)
                {
                    match = Global.RegexInternalFunction.Match(trimmedOperand);
                    if (match.Groups["name"].Success)
                    {
                        matched = true;
                        output = InternalFunctions.GetValue(match.Groups["name"].Value, requestSpec);
                    }
                }

                if (!matched)
                {
                    match = Global.RegexRequestSpec.Match(trimmedOperand);
                    if (match.Groups["name"].Success)
                    {
                        matched = true;
                        output = requestSpec[match.Groups["name"].Value];
                    }
                }

                if (!matched)
                {
                    //otherwise just return literal value
                    output = trimmedOperand;
                }
            }
            
//            Logger.DebugFormat("Got value for {0} = {1}", operand, output);

            //return null if value cannot be determined
            //if (output == null)
                //throw new Exception(operand + " evaluated to null");

            return output;
        }
    }
}

